/*=+--+=#=+--     Unmanned Aerial System Management Software      --+=#=+--+=#*\
|          Copyright (C) 2011 Regents of the University of Colorado.           |
|                             All Rights Reserved.                             |

     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as
     published by the Free Software Foundation.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

            Jack Elston                       Cory Dixon                        
|           elstonj@colorado.edu              dixonc@colorado.edu              |
|                                                                              |
\*=+--+=#=+--                 --+=#=+--+=#=+--                    --+=#=+--+=#*/

#include "commPage.h"
#include "myStripChart.h"
#include "vcConfig.h"

/*<---Extern Variables---->*/
extern GuiStruct  guiStr;
extern FifoMsg_t *fifo;

bool CommPage::create_page(Client *client, GtkBuilder *builder)
{	
	if( !client ) {
		cout << "CommPage::create_page -- invalid client " << endl;
		return false;
	}
	if( page ) {
		cout << "CommPage::update -- page has already been created" << endl;
		return true;
	}
	if( !builder ) {
		cout << "CommPage::update -- invalid xml builder" << endl;
		return false;
	}

	page_client = client;
	page        = GTK_WIDGET (gtk_builder_get_object(builder, "comm_page"));
	num_clients = GTK_WIDGET (gtk_builder_get_object(builder, "comm_num_clients"));
	bytes_tx    = GTK_WIDGET (gtk_builder_get_object(builder, "comm_bytes_tx"));
	bytes_rx    = GTK_WIDGET (gtk_builder_get_object(builder, "comm_bytes_rx"));
	rate_tx     = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_tx"));
	rate_rx     = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_rx"));
	rate_plot   = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_plot"));
	node_rssi   = GTK_WIDGET (gtk_builder_get_object(builder, "comm_node_rssi"));
	comm_rate_test      = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_test"));
	comm_rate_test_time = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_test_time"));
	comm_rate_scale     = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_scale"));
	comm_rate_tcp       = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_tcp"));
	comm_rate_udp       = GTK_WIDGET (gtk_builder_get_object(builder, "comm_rate_udp"));

	if( !( page && num_clients && bytes_tx && bytes_rx &&
	       rate_tx && rate_rx && rate_plot && node_rssi &&
	       comm_rate_test && comm_rate_test_time && comm_rate_scale && 
			 comm_rate_tcp && comm_rate_udp )
	) {
		cout << "CommPage::create_page - did not find all required widgets in xml file" << endl;	
		page = NULL;
		return false;
	}

	// get buttons, and add signal handlers
	g_signal_connect(G_OBJECT(comm_rate_test), "clicked", G_CALLBACK(CommPage::tp_test_clicked), this);

	//
	gtk_range_set_range( GTK_RANGE(comm_rate_scale), 0.0, 0.1);
	gtk_range_set_fill_level( GTK_RANGE(comm_rate_scale), 0.0);
	gtk_range_set_value( GTK_RANGE(comm_rate_scale), 0.0);
	throughput_test_running = false;
	max_throughput = 0;
	throughput_val = 0;

	// Rate plot 
	int setNum        = 2;
	const gchar *setNames[] = { "Rx", "Tx" }; 
	void * setPtr[]   = { &total_rate_rx, &total_rate_tx };
	int setType[]     = { FLOAT_T, FLOAT_T};

	String chart_name = page_client->name;
	chart_name += "/";
	chart_name += "rates";

	GtkStripChart * graph = gtkstripchart_new (setNum, setNames, setPtr, setType, chart_name.c_str());
	graph->parent = rate_plot;

	gtk_container_add (GTK_CONTAINER (rate_plot),  graph->main_widget);
	gtk_container_set_border_width (GTK_CONTAINER (graph->main_widget), GNOME_PAD_SMALL);

	gtkstripchart_start (graph);

	// rssi list
	create_node_rssi_tree();

	return true;
}

void CommPage::update()
{
	if( !page_client ) {
		//cout << " - invalid page_client" << endl;
		return;
	}
	//-- See if the page has been initialized
	if( page == NULL ) {
		cout << "CommPage::update -- page has not been created" << endl;
		return;
	}

	// don't do work if we don't need too
	if( !GTK_WIDGET_VISIBLE( page ) ) {
		return;
	}

	// cumulative values
	int numClients = 0;
	double B_rx = 0, B_tx = 0;
	double R_tx = 0, R_rx = 0;

	for(int li=0; li < MAX_CLIENTS; li++ ) {
		if( page_client->linkstatus[li].last_rx > 0 ) {
			numClients++;	
			B_rx += page_client->linkstatus[li].numBytesRx;
			B_tx += page_client->linkstatus[li].numBytesTx;

			if( !IsNaN(page_client->linkstatus[li].rateTx) )
				R_tx += page_client->linkstatus[li].rateTx;
			if( !IsNaN(page_client->linkstatus[li].rateRx) )
				R_rx += page_client->linkstatus[li].rateRx;
		}
	}

	// convert to kB
	B_rx /= 1024.0;
	B_tx /= 1024.0;

	// save the values for the plot
	total_rate_tx = R_tx;
	total_rate_rx = R_rx;

	// num page_clients
	gchar *text;
	text = g_strdup_printf ("%i", numClients);
	gtk_entry_set_text (GTK_ENTRY (num_clients), text);
	g_free (text);
		
	// num bytes tx
	text = g_strdup_printf ("%.1lf", B_tx);
	gtk_entry_set_text (GTK_ENTRY (bytes_tx), text);
	g_free (text);
	
	// num bytes rx
	text = g_strdup_printf ("%.1lf", B_rx);
	gtk_entry_set_text (GTK_ENTRY (bytes_rx), text);
	g_free (text);
	
	// rate rx
	text = g_strdup_printf ("%.1lf", R_rx);
	gtk_entry_set_text (GTK_ENTRY (rate_rx), text);
	g_free (text);

	// rate rx
	text = g_strdup_printf ("%.1lf", R_tx);
	gtk_entry_set_text (GTK_ENTRY (rate_tx), text);
	g_free (text);

	// update the rssi list
	update_link_list();
	
	// update the rssi list
	update_throughput_test();

	// redraw page
	gtk_widget_queue_draw(page);	
}

/* columns for GtkTree */
enum
{
	IND_COLUMN = 0,
	NAME_COLUMN,
	RSSI_COLUMN,
	NUM_COLUMNS
};

void CommPage::create_node_rssi_tree()
{
	// create tree store model
	list_store = gtk_list_store_new ( NUM_COLUMNS,
	                                  G_TYPE_INT,     // link ind
	                                  G_TYPE_STRING,  // name
	                                  G_TYPE_STRING   // rssi - we convert to string ourselves
	                                );
	// draw the view of the tree
	format_treeview();
	//setColor( GTK_WIDGET(treeview) );

	// add to container
	gtk_widget_show( GTK_WIDGET(treeview) );
	gtk_container_add( GTK_CONTAINER (node_rssi), GTK_WIDGET(treeview) );
}
	

void CommPage::format_treeview()
{
	gint col_offset;
	GtkCellRenderer *renderer;

	// create tree view 
	treeview = GTK_TREE_VIEW(gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store)));

	// setup tree functionality
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_grid_lines(GTK_TREE_VIEW(treeview), GTK_TREE_VIEW_GRID_LINES_BOTH);
	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_reorderable(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(treeview), FALSE);

	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
	gtk_tree_selection_set_mode( selection, GTK_SELECTION_NONE);

	// column for node names 
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (renderer, "xalign", 0.0, NULL);
	col_offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
			-1, "Node", renderer, 
			"text", NAME_COLUMN,
			NULL);

	// column for node rssi
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (renderer, "xalign", 1.0, NULL);
	g_object_set_data (G_OBJECT (renderer), "column", (gint *)RSSI_COLUMN);

	col_offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
			-1, "RSSI", renderer, 
			"text", RSSI_COLUMN,
			NULL);

}

void CommPage::update_link_list()
{
	GtkTreeIter iter;
	char idStr[32];
	char rssiStr[32];

	for(int c_ind=0; c_ind < MAX_CLIENTS; c_ind++ ) {
		if( page_client->linkstatus[c_ind].last_rx > 0 ) {

			sprintf(rssiStr,"%.1f", page_client->linkstatus[c_ind].signal);

			//-- fill in the name of the node --//
			// if we don't know ip, use mac. if ip, check
			// to see if we can get a name from the clien lise
			if( page_client->linkstatus[c_ind].ip[0] == 0 ) {
				sprintf(idStr,"%02X:%02X:%02X:%02X:%02X:%02X",
						page_client->linkstatus[c_ind].mac[0],
						page_client->linkstatus[c_ind].mac[1],
						page_client->linkstatus[c_ind].mac[2],
						page_client->linkstatus[c_ind].mac[3],
						page_client->linkstatus[c_ind].mac[4],
						page_client->linkstatus[c_ind].mac[5]);
			} else {
				sprintf(idStr,"%i.%i.%i.%i",
						page_client->linkstatus[c_ind].ip[0],
						page_client->linkstatus[c_ind].ip[1],
						page_client->linkstatus[c_ind].ip[2],
						page_client->linkstatus[c_ind].ip[3]);

				// check to see if we know the node name
				in_addr addr;
				addr.s_addr = *((in_addr_t *)page_client->linkstatus[c_ind].ip);
				Client * b_client = guiStr.shm_ptr->clients.getClient(addr);
				if( b_client )
					strcpy(idStr, b_client->name.c_str());
			}

			//-- walk through the list looking for the link in the tree model
			// if empty, just fill in new value
			bool valid = gtk_tree_model_get_iter_first( GTK_TREE_MODEL(list_store), &iter);
			while( valid ) {
				int link_ind;
				gtk_tree_model_get (GTK_TREE_MODEL(list_store), &iter, IND_COLUMN, &link_ind, -1);

				// if this is the current link
				if( link_ind == c_ind )
					break;

				valid = gtk_tree_model_iter_next ( GTK_TREE_MODEL(list_store), &iter);
			}
			// if we still have a valid iteration, we found the current link
			// already in the model, if not add new one
			if( !valid )
				gtk_list_store_append (list_store, &iter);

			//-- fill in the gtk tree menu --//
			gtk_list_store_set (list_store, &iter,
					IND_COLUMN,  c_ind,
					NAME_COLUMN, idStr,
					RSSI_COLUMN, rssiStr,
					-1);

		}
	}
}

void CommPage::update_throughput_test()
{
	if( throughput_test_running ) {
		throughput_val = page_client->comm[0].rx_rate;
		//cout << "CommPage::update_throughput_test - val=" << throughput_val 
		       //<< " max=" << max_throughput << endl;
		if( max_throughput <= throughput_val ) {
			max_throughput = throughput_val;
			gtk_range_set_range(GTK_RANGE(comm_rate_scale), 0.0, max_throughput + 10.0);
			gtk_range_set_fill_level(GTK_RANGE(comm_rate_scale), max_throughput);
		}
		gtk_range_set_value(GTK_RANGE(comm_rate_scale), throughput_val);

		// update page
		if( page )
			gtk_widget_queue_draw(page);

		//throughput_test_running = false;
	}
}

//-----------------------------------------------------------
// button handlers -- we pass a WindowStruct pointer
void CommPage::tp_test_clicked(GtkButton *button, gpointer data)
{
	//cout << "CommPage::tp_test_clicked" << endl;
	
	CommPage *comm = (CommPage *)data;
	if( !comm ) return;

	comm->test_type = NO_TEST;
	comm->test_time = 0;

	// get status of button
	if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON (button) ) )
	{
		// get test time
		comm->test_time = gtk_spin_button_get_value(GTK_SPIN_BUTTON(comm->comm_rate_test_time) );

		// get test type
		comm->test_type = UDP_TEST;
		if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON (comm->comm_rate_tcp) ) )
			comm->test_type = TCP_TEST;
	}

	// fill in values
	RateTest_t *test = (RateTest_t *)CMD_DATAPTR(fifo->data);
	test->type = comm->test_type;
	test->time = (uint16_t) comm->test_time;

	Client *me = guiStr.shm_ptr->clients.getClient(0);
	fifo->src.s_addr  = guiStr.shm_ptr->clients.getAddr(me, comm->page_client).s_addr;
	fifo->dest.s_addr = guiStr.shm_ptr->clients.getAddr(comm->page_client).s_addr;

	// send test command
	CMD_CMD(fifo->data)  = CMD_RATE_TEST;
	CMD_NUM(fifo->data)  = sizeof(RateTest_t);
	CMD_CSUM(fifo->data) = 0;
	CMD_CSUM(fifo->data) = C_calcCheckSum((uint8_t*)fifo->data);

	fifo->size = CMD_SIZE(fifo->data);
	guiStr.vcgui->writeFifo(ThreadedPipe::INBOARD_TX);

	printf("<-- Sent %s CMD_RATE_TEST: Type=%i Time=%i -->\n", 
	        comm->page_client->name.c_str(), test->type, test->time);	

	comm->set_sent_state(GTK_WIDGET(button));

	return;
}

// use this function to indicate a command or ACK has been received
void CommPage::setCMD_RX(uint8_t cmd_id, int ack_nack)
{
	//cout << "CommPage::setCMD_RX - [" << (int)cmd_id << "]  ackNack=" << ack_nack << endl;
	
	switch( cmd_id ) {
		case CMD_RATE_TEST:
			if( ack_nack && test_type > NO_TEST) 
				throughput_test_running = true;
			else 
				throughput_test_running = false;

			set_rx_state(comm_rate_test, ack_nack);
			break;
		default:
			break;
	}

	// update page
	if( page )
		gtk_widget_queue_draw(page);
}
